/*
Copyright 2010,2011 Matt Van Der Westhuizen

This file is part of CGLL.

CGLL is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

CGLL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with CGLL.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.chaoticengine.cgll;

import de.matthiasmann.twl.GUI;
import de.matthiasmann.twl.Widget;
import de.matthiasmann.twl.renderer.Renderer;
import de.matthiasmann.twl.renderer.lwjgl.LWJGLRenderer;
import de.matthiasmann.twl.theme.ThemeManager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import org.chaoticengine.cgll.entity.EntityManager;
import org.chaoticengine.cgll.serialization.CircleConverter;
import org.chaoticengine.cgll.serialization.ColorConverter;
import org.chaoticengine.cgll.serialization.RectangleConverter;
import org.chaoticengine.cgll.serialization.Vector2fConverter;
import org.chaoticengine.cgll.twl.RootPane;
import org.chaoticengine.cgll.twl.TWLInputForwarder;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Circle;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.state.GameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.util.ResourceLoader;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.convert.Registry;
import org.simpleframework.xml.convert.RegistryStrategy;
import org.simpleframework.xml.core.Persister;
import org.simpleframework.xml.filter.Filter;
import org.simpleframework.xml.strategy.Strategy;

/**
 *
 * @author Matt Van Der Westhuizen <mattpwest@gmail.com>
 */
public abstract class CGLLStateBasedGame extends StateBasedGame {
    private final Widget emptyRootWidget;
    private String guiTheme = null;
    private GUI gui = null;
    private TWLInputForwarder inputForwarder = null;
    private int score = 0;

    // Serializer for the game
    protected Serializer serial = null;
    protected Registry serialRegistry = null;
    protected Strategy serialStrategy = null;

    public CGLLStateBasedGame(String windowTitle) {
        super(windowTitle);

        initSerializer();

        emptyRootWidget = null;
    }

    public CGLLStateBasedGame(String windowTitle, String guiThemeFile) {
        super(windowTitle);

        initSerializer();

        guiTheme = guiThemeFile;
        emptyRootWidget = new Widget();
        emptyRootWidget.setTheme("");
    }

    public abstract void addStates(GameContainer container);

    private void initSerializer() {
        // Initialize serialization infrastructure
        serialRegistry = new Registry();
        serialStrategy = new RegistryStrategy(serialRegistry);
        Filter serialFilter = new Filter() {
            public String replace(String string) {
                String expression = string;

                if (!string.startsWith("${")) {
                    return(string);
                }

                if (!string.endsWith("}")) {
                    return(string);
                }

                string = string.substring(2);
                string = string.substring(0, string.length() - 2);

                // Use reflection
                Object obj = null;
                String object = string.substring(string.indexOf("."));
                string = string.substring(string.indexOf(".") + 1);
                while ((object != null) && (!object.equals(""))) {
                    String methodName = "get" + Character.toUpperCase(object.charAt(0))
                            + object.substring(1);
                    Method mthd = null;
                    EntityManager entityMgr = ((CGLLGameState) getCurrentState()).getEntityManager();
                    try {
                        mthd = entityMgr.getClass().getMethod(methodName, new Class[] {});

                        if (obj == null) {
                            obj = mthd.invoke(entityMgr);
                        } else {
                            obj = mthd.invoke(obj);
                        }
                    } catch (NoSuchMethodException nsmEx) {
                        System.out.println("Error: No such method '" + methodName + "' in expression '" + expression + "'.");
                        nsmEx.printStackTrace();
                        System.exit(-1);
                    } catch (IllegalAccessException iaEx) {
                        System.out.println("Error: Illegal method access '" + methodName + "' in expression '" + expression + "'.");
                        iaEx.printStackTrace();
                        System.exit(-1);
                    } catch (IllegalArgumentException iargEx) {
                        System.out.println("Error: Illegal argument on '" + methodName + "' in expression '" + expression + "'.");
                        iargEx.printStackTrace();
                        System.exit(-1);
                    } catch (InvocationTargetException itEx) {
                        System.out.println("Error: Invocation target exception on '" + methodName + "' in expression '" + expression + "'.");
                        itEx.printStackTrace();
                        System.exit(-1);
                    } catch (SecurityException sEx) {
                        System.out.println("Error: Security exception on '" + methodName + "' in expression '" + expression + "'.");
                        sEx.printStackTrace();
                        System.exit(-1);
                    }

                    if (string.indexOf(".") >= 0) {
                        object = string.substring(string.indexOf("."));
                        string = string.substring(string.indexOf(".") + 1);
                    } else {
                        object = string;
                        string = "";
                    }
                }

                if (obj != null) {
                    return(obj.toString());
                } else {
                    return("null");
                }
            }
        };
        serial = new Persister(serialStrategy, serialFilter);

        try {
            serialRegistry.bind(Vector2f.class, Vector2fConverter.class);
            serialRegistry.bind(Rectangle.class, RectangleConverter.class);
            serialRegistry.bind(Circle.class, CircleConverter.class);
            serialRegistry.bind(Color.class, ColorConverter.class);
        } catch (Exception ex) {
            System.out.println("Error: CGLL failed to bind Slick2D serialization converters.");
            ex.printStackTrace();
        }
    }

    @Override
    public void addState(GameState state) {
        if (!(state instanceof CGLLGameState)) {
            throw new IllegalArgumentException("state must be a CGLLGameState");
        }

        super.addState(state);
    }

    public void addState(CGLLGameState state) {
        super.addState(state);
    }

    // Init the game states for the menus
    @Override
    public void initStatesList(GameContainer gc) throws SlickException {
        addStates(gc);

        initGUI(gc);
    }

    protected void handleStateChange() {
        if (gui == null) {
            return;
        }

        CGLLGameState state = (CGLLGameState) this.getCurrentState();
        RootPane rp = state.getRootPane();
        if (rp != null) {
            gui.setRootPane(rp);
            state.layoutRootPane();
            getContainer().getInput().addPrimaryListener(inputForwarder);
        } else {
            gui.setRootPane(emptyRootWidget);
            getContainer().getInput().removeListener(inputForwarder);
        }
    }

    @Override
    protected void postRenderState(GameContainer container, Graphics g) throws SlickException {
        super.postRenderState(container, g);

        if (gui != null) {
            gui.draw();
        }
    }

    @Override
    protected void postUpdateState(GameContainer container, int delta) throws SlickException {
        super.postUpdateState(container, delta);

        if (gui != null) {
            gui.setSize();
            gui.handleTooltips();
            gui.updateTimers();
            gui.invokeRunables();
            gui.validateLayout();
            gui.setCursor();
        }
    }

    public GUI getGUI() {
        return gui;
    }

    protected void initGUI(GameContainer gc) throws SlickException {
        // Initialize the TWL GUI if required
        if (guiTheme != null) {
            GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
            try {
                Renderer renderer = new LWJGLRenderer();
                URL themeURL = ResourceLoader.getResource(guiTheme);
                ThemeManager theme = ThemeManager.createThemeManager(themeURL, renderer);

                gui = new GUI(emptyRootWidget, renderer);
                gui.applyTheme(theme);

                Input input = gc.getInput();
                inputForwarder = new TWLInputForwarder(gui, input);
            } catch (Throwable e) {
                throw new SlickException("Could not initialize TWL GUI", e);
            } finally {
                GL11.glPopAttrib();
            }
        }
    }

    public Serializer getSerializer() {
        return(serial);
    }

    public int getScore() {
        return(score);
    }

    public void setScore(int score) {
        this.score = score;
    }

    public void addScore(int score) {
        this.score += score;
    }
}
